/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.solr.servlet;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;

/**
 * Admin JDK Logger level report and selection servlet.
 * 
 * @version $Id: LogLevelSelection.java 1065312 2011-01-30 16:08:25Z rmuir $
 * @since solr 1.3
 */
public final class LogLevelSelection extends HttpServlet {
	@Override
	public void init() throws ServletException {
	}

	/**
	 * Processes an HTTP GET request and changes the logging level as specified.
	 */
	@Override
	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {
		// Output page

		response.setContentType("text/html");
		PrintWriter out = response.getWriter();
		out.write("<html><head>\n");
		out.write("<title>Solr Admin: JDK Log Level Selector</title>\n");
		out.write("<link rel=\"stylesheet\" type=\"text/css\" href=\"solr-admin.css\" />");
		out.write("</head><body>\n");
		out.write("<a href=\".\"><img border=\"0\" align=\"right\" height=\"78\" width=\"142\" src=\"solr_small.png\" alt=\"Solr\"></a>");
		out.write("<h1>JDK Log Level Selector</h1>");

		out.write("<p>Below is the complete JDK Log hierarchy with "
				+ "intermediate logger/categories synthesized.  "
				+ "The effective logging level is shown to the "
				+ "far right. If a logger has unset level, then "
				+ "the effective level is that of the nearest ancestor "
				+ "with a level setting.  Note that this only shows "
				+ "JDK Log levels.</p>\n");

		out.write("<form method='POST'>\n");

		out.write("<input type='submit' name='submit' value='set' "
				+ "class='button'>\n");
		out.write("<input type='submit' name='submit' value='cancel' "
				+ "class='button'>\n");
		out.write("<br><br>\n");

		out.write("<table cellspacing='2' cellpadding='2'>");

		out.write("<tr bgcolor='#CCCCFF'>"
				+ "<th align=left>Logger/Category name<br>"
				+ "<th colspan=9>Level</th>" + "</tr><tr bgcolor='#CCCCFF'>"
				+ "<td bgcolor='#AAAAAA'>"
				+ "(Dark rows don't yet exist.)</td>");

		for (int j = 0; j < LEVELS.length; ++j) {
			out.write("<th align=left>");
			if (LEVELS[j] != null)
				out.write(LEVELS[j].toString());
			else
				out.write("unset");
			out.write("</th>");
		}
		out.write("<th align=left>Effective</th>\n");
		out.write("</tr>\n");

		Iterator iWrappers = buildWrappers().iterator();
		while (iWrappers.hasNext()) {

			LogWrapper wrapper = (LogWrapper) iWrappers.next();

			out.write("<tr");
			if (wrapper.logger == null) {
				out.write(" bgcolor='#AAAAAA'");
			}
			// out.write( ( wrapper.logger != null ) ? "#DDDDDD" : "#AAAAAA" );
			out.write("><td>");
			if ("".equals(wrapper.name)) {
				out.write("root");
			} else {
				out.write(wrapper.name);
			}
			out.write("</td>\n");
			for (int j = 0; j < LEVELS.length; ++j) {
				out.write("<td align=center>");
				if (!wrapper.name.equals("root") || (LEVELS[j] != null)) {
					out.write("<input type='radio' name='");
					if ("".equals(wrapper.name)) {
						out.write("root");
					} else {
						out.write(wrapper.name);
					}
					out.write("' value='");
					if (LEVELS[j] != null)
						out.write(LEVELS[j].toString());
					else
						out.write("unset");
					out.write('\'');
					if (LEVELS[j] == wrapper.level())
						out.write(" checked");
					out.write('>');
				}
				out.write("</td>\n");
			}
			out.write("<td align=center>");
			if (wrapper.logger != null) {
				out.write(getEffectiveLevel(wrapper.logger).toString());
			}
			out.write("</td></tr>\n");
		}
		out.write("</table>\n");

		out.write("<br>\n");
		out.write("<input type='submit' name='submit' value='set' "
				+ "class='button'>\n");
		out.write("<input type='submit' name='submit' value='cancel' "
				+ "class='button'>\n");

		out.write("</form>\n");

		out.write("</body></html>\n");
	}

	@Override
	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws IOException, ServletException {
		if (request.getParameter("submit").equals("set")) {

			Map paramMap = request.getParameterMap();

			Iterator iParams = paramMap.entrySet().iterator();
			while (iParams.hasNext()) {
				Map.Entry p = (Map.Entry) iParams.next();
				String name = (String) p.getKey();
				String value = ((String[]) p.getValue())[0];

				if (name.equals("submit"))
					continue;
				Logger logger;
				LogManager logManager = LogManager.getLogManager();
				if ("root".equals(name)) {
					logger = logManager.getLogger("");
				} else
					logger = logManager.getLogger(name);

				if ("unset".equals(value)) {
					if ((logger != null) && (logger.getLevel() != null)) {
						logger.setLevel(null);
						log.info("Unset log level on '" + name + "'.");
					}
				} else {
					Level level = Level.parse(value);
					if (logger == null)
						logger = Logger.getLogger(name);
					if (logger.getLevel() != level) {
						logger.setLevel(level);
						log.info("Set '" + name + "' to " + level + " level.");
					}
				}
			}
		} else {
			log.fine("Selection form cancelled");
		}

		// Redirect back to standard get page.
		response.sendRedirect(request.getRequestURI());
	}

	private Collection buildWrappers() {
		// Use tree to get sorted results
		SortedSet<LogWrapper> roots = new TreeSet<LogWrapper>();

		roots.add(LogWrapper.ROOT);

		LogManager logManager = LogManager.getLogManager();

		Enumeration<String> loggerNames = logManager.getLoggerNames();
		while (loggerNames.hasMoreElements()) {
			String name = loggerNames.nextElement();
			Logger logger = Logger.getLogger(name);
			LogWrapper wrapper = new LogWrapper(logger);
			roots.remove(wrapper); // Make sure add occurs
			roots.add(wrapper);

			while (true) {
				int dot = name.lastIndexOf(".");
				if (dot < 0)
					break;
				name = name.substring(0, dot);
				roots.add(new LogWrapper(name)); // if not already
			}
		}

		return roots;
	}

	private Level getEffectiveLevel(Logger logger) {
		Level level = logger.getLevel();
		if (level != null) {
			return level;
		}
		for (Level l : LEVELS) {
			if (l == null) {
				// avoid NPE
				continue;
			}
			if (logger.isLoggable(l)) {
				// return first level loggable
				return l;
			}
		}
		return Level.OFF;
	}

	private static class LogWrapper implements Comparable {
		public static LogWrapper ROOT = new LogWrapper(LogManager
				.getLogManager().getLogger(""));

		public LogWrapper(Logger logger) {
			this.logger = logger;
			this.name = logger.getName();
		}

		public LogWrapper(String name) {
			this.name = name;
		}

		public int compareTo(Object other) {
			if (this.equals(other))
				return 0;
			if (this == ROOT)
				return -1;
			if (other == ROOT)
				return 1;

			return name.compareTo(((LogWrapper) other).name);
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			LogWrapper other = (LogWrapper) obj;
			if (name == null) {
				if (other.name != null)
					return false;
			} else if (!name.equals(other.name))
				return false;
			return true;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		public Level level() {
			if (logger != null)
				return logger.getLevel();
			return null;
		}

		public Logger logger = null;
		public String name;
	}

	private static Level[] LEVELS = {
			null, // aka unset
			Level.FINEST, Level.FINE, Level.CONFIG, Level.INFO, Level.WARNING,
			Level.SEVERE, Level.OFF
	// Level.ALL -- ignore. It is useless.
	};

	private Logger log = Logger.getLogger(getClass().getName());
}
